package main;

import static util.Constants.DEFAULT_FRAME_RATE;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLJPanel;
import javax.media.opengl.glu.gl2.GLUgl2;
import javax.swing.JFrame;
import javax.vecmath.Matrix4f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import shader.ShaderProgram;
import simulation.Simulator;
import util.Timing;
import util.Util;

import com.jogamp.opengl.util.FPSAnimator;
import com.jogamp.opengl.util.awt.Screenshot;
import com.jogamp.opengl.util.awt.TextRenderer;

/**
 * The class MotionSimulator provides suitable default values, but subclasses should change
 * these. Subclasses should override the initialize() method in order
 * to set up their own windows.
 *
 * @author Colin Ponce
 * @author Jeran Fox
 * @author Matthew Cong
 * @author Teddy Ni
 * @author Yunfeng Bai
 */
public class MotionSimulator implements GLEventListener
{
    /** An object that holds the features of our OpenGL version. */
	private GLProfile glProfile;

    /** Allows our application to run at some fixed frame rate. */
	private FPSAnimator animator;

    /** Frame rate of our application. */
    private int frameRate;

    /** Swing component that allows OpenGL drawing. */
	private GLJPanel panel;

    /** Captures video frames. */
	private FrameExporter frameExporter;

    /** Event listener used to respond to user input. */
	private JOGLEventListener listener;
	
	private JFrame frame;
	
	private final int FPS_OBSERVE_INTERVAL = 60;
    
    /**
     * Camera object. Observe that the camera can be simulated as well,
     * which provides for potentially interesting animations.
     */
    private Camera camera = new Camera("Camera", new Point3d(-5.0, 5.0, 10.0));

    /** Width of our application window. */
    private int width;

    /** Height of our application window. */
    private int height;

    /**
     * Flag to determine whether setup of our graphics application is
     * finished. If so, we can respond to drawing events and input.
     */
	private boolean ready;
	
	// Modes.
	private boolean stepping = false;
	private boolean forwardStep = false;
	private boolean renderBoundingBoxMode = false;
	
	private Simulator simulator;
	
	// Lighting information.
	private float[] lightAmbient, lightDiffuse, lightSpecular, lightDirection, lightPosition;
	private float[] lightFrustum; // left, right, bottom, top, near, far
	
	// Textures.
	private int shadowMapTextureId;
	private int mainColorTextureId, mainDepthTextureId, mainNormalTextureId, mainPositionTextureId;
	private int aoTextureId, blurredAoTextureId;
	private int randomTextureId;
	
	private int randomTextureSize = 16;
	
	private final int shadowMapHeight = 1024, shadowMapWidth = 1024;

	// Shader programs.
	private ShaderProgram plainProgram;
	private ShaderProgram renderWithShadowMapProgram, displayShadowMapProgram, deferredShadingProgram, aoProgram;
	
	// Frame buffers.
	private int shadowMapFrameBufferId, mainFrameBufferId, aoFrameBufferId, blurFrameBufferId;
	
	private int canvasHeight, canvasWidth;
	private int windowHeight = 768, windowWidth = 1024;
	
	// Text renderers.
	private TextRenderer fpsTextRenderer, modeTextRenderer;
	
	private LinkedList<Long> frameTimePoints = new LinkedList<Long>();
	
	private long lastFrameTime = 0;
	
	// Uniform variable locations for renderWithShadowMapProgram.
	int viewMatrixUniformId;
	int shadowMapUniformId;
	int lightMatrixUniformId;
	int lightAmbientUniformId;
	int lightDiffuseUniformId;
	int lightSpecularUniformId;
	int lightDirectionUniformId;
	int eyePositionUniformId;
	
	// Uniform variable locations for deferredShadingProgram.
	int colorTextureUniformId;
	int aoUniformId;

	// Uniform variable locations for aoProgram.
	int normalTextureUniformId;
	int positionTextureUniformId;
	int randomTextureUniformId;
	
	// Uniform variable locations for displayShadowMapProgram.
	int textureUniformId;
	
    /**
     * Set up a basic OpenGL application with default values.
     */
    public MotionSimulator()
    {
    	Test test = new Test();
    	test.testAll();
    	
        ready = false;
        
        initializeObjects();

        panel.addGLEventListener(this);
        panel.addKeyListener(listener);
		panel.addMouseListener(listener);
		panel.addMouseMotionListener(listener);
		panel.setFocusable(true);
		
		frame.setContentPane(panel);
		frame.setSize(new Dimension(windowWidth, windowHeight));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
		
		Dimension d = panel.getSize();
		canvasWidth = d.width;
		canvasHeight = d.height;
		
		setFrameRate(DEFAULT_FRAME_RATE);
    }
    
    private void initializeObjects()
	{
    	// Set up OpenGL profile and canvas.
		glProfile = GLProfile.getDefault();
		GLCapabilities caps = new GLCapabilities(glProfile);
		panel = new GLJPanel(caps);

        listener = new JOGLEventListener(this);
        
        animator = new FPSAnimator(panel, frameRate);
        
        frame = new JFrame("Motion Simulator");
        
        fpsTextRenderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 36));
        modeTextRenderer = new TextRenderer(new Font("SansSerif", Font.PLAIN, 18));
	}

	private void initializeLighting(GL2 gl_)
    {
        gl_.glShadeModel(GL2.GL_SMOOTH);
        lightAmbient = new float[] { 0.3f, 0.3f, 0.3f, 1.0f };
        lightDiffuse = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
        lightSpecular = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
        lightDirection = new float[] { -1.0f, -1.0f, -1.0f, 0.0f }; // directional light
        lightPosition = new float[] { 10.0f, 10.0f, 10.0f, 0.0f }; // used as the position to capture shadow map.
        
        // left, right, bottom, top, near, far
        lightFrustum = new float[] { -10, 10, -10.0f, 10.0f, 0.1f, 30 };
    }
	
	private void initRenderWithShadowMapProgramUniformLocations(GL2 gl)
	{
		viewMatrixUniformId = renderWithShadowMapProgram.getUniformLocation(gl, "viewMatrix");
		shadowMapUniformId = renderWithShadowMapProgram.getUniformLocation(gl, "shadowMap");
		lightMatrixUniformId = renderWithShadowMapProgram.getUniformLocation(gl, "lightViewProjectionBias");
		lightAmbientUniformId = renderWithShadowMapProgram.getUniformLocation(gl, "lightAmbient");
		lightDiffuseUniformId = renderWithShadowMapProgram.getUniformLocation(gl, "lightDiffuse");
		lightSpecularUniformId = renderWithShadowMapProgram.getUniformLocation(gl, "lightSpecular");
		lightDirectionUniformId= renderWithShadowMapProgram.getUniformLocation(gl, "lightDirection");
		eyePositionUniformId = renderWithShadowMapProgram.getUniformLocation(gl, "eyePosition");
	}
	
	private void initDeferredShadingProgramUniformLocations(GL2 gl)
	{
		colorTextureUniformId = deferredShadingProgram.getUniformLocation(gl, "colorTexture");
		aoUniformId = deferredShadingProgram.getUniformLocation(gl, "aoTexture");
	}
	
	private void initAOProgramUniformLocations(GL2 gl)
	{
		normalTextureUniformId = aoProgram.getUniformLocation(gl, "normalTexture");
		positionTextureUniformId = aoProgram.getUniformLocation(gl, "positionTexture");
		randomTextureUniformId = aoProgram.getUniformLocation(gl, "randomTexture");
	}
	
	private void initDisplayShadowMapProgram(GL2 gl)
	{
		textureUniformId = displayShadowMapProgram.getUniformLocation(gl, "texture");
	}
    
    /**
     * Called by OpenGL to initialize drawing.
     *
     * @param GLAutoDrawable a graphics context
     */
	public void init(GLAutoDrawable drawable_)
    {
		GL2 gl = drawable_.getGL().getGL2();
		
        // Default OpenGL settings.
		gl.glClearColor(0, 0, 0, 1);
		gl.glShadeModel(GL2.GL_SMOOTH);

		// Depth buffer.
		gl.glClearDepth(1);
		gl.glDepthFunc(GL2.GL_LEQUAL);
		gl.glEnable(GL2.GL_DEPTH_TEST);
		
        // Cull faces.
		gl.glEnable(GL2.GL_CULL_FACE);

        // Try to use nice perspective.
		gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL2.GL_NICEST);
		
		initializeLighting(gl);
		
		// Create shader programs.
		renderWithShadowMapProgram = new ShaderProgram(gl, "src\\shader\\renderWithShadowmap.vertex",
												 "src\\shader\\renderWithShadowmap.fragment");
		initRenderWithShadowMapProgramUniformLocations(gl);
		displayShadowMapProgram =
				new ShaderProgram(gl, "src\\shader\\displayshadowmap.vertex", "src\\shader\\displayshadowmap.fragment");
		initDisplayShadowMapProgram(gl);
		aoProgram =
				new ShaderProgram(gl, "src\\shader\\ssao.vertex", "src\\shader\\ssao.fragment");
		initAOProgramUniformLocations(gl);
//		blurProgram =
//				new ShaderProgram(gl, "src\\shader\\blur.vertex", "src\\shader\\blur.fragment");
		deferredShadingProgram =
				new ShaderProgram(gl, "src\\shader\\deferredShading.vertex", "src\\shader\\deferredShading.fragment");
		initDeferredShadingProgramUniformLocations(gl);
		plainProgram =
				new ShaderProgram(gl, "src\\shader\\plainDisplay.vertex", "src\\shader\\plainDisplay.fragment");
		
		// Create the shadow map texture.
		IntBuffer textures = IntBuffer.allocate(8);
		gl.glGenTextures(8, textures);
		shadowMapTextureId = textures.get(0);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, shadowMapTextureId);
		gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight, 0, GL2.GL_DEPTH_COMPONENT, GL2.GL_FLOAT, null);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		// Create the main rendering texture.
		mainColorTextureId = textures.get(1);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, mainColorTextureId);
		gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGBA, canvasWidth, canvasHeight, 0, GL2.GL_RGBA, GL2.GL_FLOAT, null);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		// Create the main depth texture.
		mainDepthTextureId = textures.get(2);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, mainDepthTextureId);
		gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_DEPTH_COMPONENT, canvasWidth, canvasHeight, 0, GL2.GL_DEPTH_COMPONENT, GL2.GL_FLOAT, null);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		// Create the main normal texture.
		mainNormalTextureId = textures.get(3);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, mainNormalTextureId);
		gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGBA, canvasWidth, canvasHeight, 0, GL2.GL_RGBA, GL2.GL_FLOAT, null);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		// Create the main position texture.
		mainPositionTextureId = textures.get(4);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, mainPositionTextureId);
		gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGBA32F, canvasWidth, canvasHeight, 0, GL2.GL_RGBA, GL2.GL_FLOAT, null);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		// Create the random texture.
		randomTextureId = textures.get(5);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, randomTextureId);
		Random rand = new Random();
		FloatBuffer randBuffer = FloatBuffer.allocate(randomTextureSize * randomTextureSize * 4);
		for (int i = 0; i < randBuffer.capacity(); i++)
			if (rand.nextBoolean())
				randBuffer.put(i, rand.nextFloat());
			else
				randBuffer.put(i, -rand.nextFloat());
		gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGBA, randomTextureSize, randomTextureSize, 0, GL2.GL_RGBA, GL2.GL_FLOAT, randBuffer);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_REPEAT);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_REPEAT);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		// Create the ao texture.
		aoTextureId = textures.get(6);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, aoTextureId);
		gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RED, canvasWidth, canvasHeight, 0, GL2.GL_RED, GL2.GL_FLOAT, null);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		// Create the blurred ao texture.
		blurredAoTextureId = textures.get(7);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, blurredAoTextureId);
		gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RED, canvasWidth, canvasHeight, 0, GL2.GL_RED, GL2.GL_FLOAT, null);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		// Create the frame buffers.
		IntBuffer buffer = IntBuffer.allocate(4);
		gl.glGenFramebuffers(4, buffer);
		shadowMapFrameBufferId = buffer.get(0);
		mainFrameBufferId = buffer.get(1);
		aoFrameBufferId = buffer.get(2);
		blurFrameBufferId = buffer.get(3);
		
        // Bind a texture as the depth attachment of shadow map FBO.
		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, shadowMapFrameBufferId);
        gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_DEPTH_ATTACHMENT, GL2.GL_TEXTURE_2D, shadowMapTextureId, 0);
        gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
        
        // Bind textures as the color, normal and depth information.
        gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, mainFrameBufferId);
        gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D, mainColorTextureId, 0);
        gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT1, GL2.GL_TEXTURE_2D, mainNormalTextureId, 0);
        gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT2, GL2.GL_TEXTURE_2D, mainPositionTextureId, 0);
        gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_DEPTH_ATTACHMENT, GL2.GL_TEXTURE_2D, mainDepthTextureId, 0);
        gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
        
        // Bind the ao texture as the output.
        gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, aoFrameBufferId);
        gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D, aoTextureId, 0);
        gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
        
        // Bind the blurred ao texture as the output.
        gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, blurFrameBufferId);
        gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D, blurredAoTextureId, 0);
        gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
		
		// Make a test scene.
		simulator = new Simulator();
		
//		TestScenes.initMassSphereTestScene(simulator);
//		TestScenes.initTwoBoxCollisionTestScene(simulator);
//		TestScenes.initSingleBoxTestScene2(simulator);
//		TestScenes.initThreeBoxCollisionTestScene(simulator);
//		TestScenes.initTwoSphereCollisionTestScene(simulator);
//		TestScenes.initBoxWithObjModelBoxCollisionTestScene(simulator);
//		TestScenes.initRestingContact(simulator);
//		TestScenes.initTwoMonkeys(simulator);
//		TestScenes.initObjectsOnTable(simulator);
		TestScenes.initObjectsDropping(simulator);
//		TestScenes.initLShapeDropOnTable(simulator);
//		TestScenes.initMonkeyDropOnTable(simulator);
//		TestScenes.initPiles(simulator);
//		TestScenes.initBoxInBoxes(simulator);
//		TestScenes.initCrash(simulator);
//		TestScenes.initSimpleCrash(simulator);
//		TestScenes.initBoxWithAngryBirdCollisionTestScene(simulator);
//		TestScenes.initAngryBirdCrash(simulator);
//		TestScenes.initOverlapBoxes(simulator);
		ready = true;
    }
  
    /**
     * Update application state in preparation for rendering by OpenGL.
     */
	private void updateSimulation()
    {
		if (stepping)
		{
			if (forwardStep)
			{
				simulator.update();
				forwardStep = false;
			}
		}
		else simulator.update();
	}
	
    /**
     * Render application. For this base class, we do some simple stuff,
     * like set up the camera angle.
     *
     * @param GLAutoDrawable a graphics context
     */
	protected void render(GLAutoDrawable drawable)
    {
		GL2 gl = drawable.getGL().getGL2();
		GLUgl2 glu = new GLUgl2();
		
		frameTimePoints.add(System.nanoTime());
		if (frameTimePoints.size() > FPS_OBSERVE_INTERVAL)
			frameTimePoints.removeFirst();
		
		if (renderBoundingBoxMode)
		{
			plainRenderingWithBoundingBox(gl);
		}
		else
		{
			try
			{
				renderShadowMap(gl, glu);
			} catch (Exception e)
			{
				e.printStackTrace();
			}
			
			directRenderingPass(gl);
			
			deferredRendering(gl, glu);
			
			drawShadowMapToScreen(gl, glu);
		}
		
		drawModesInfo(drawable);
		
		drawFPS(drawable);
	}
	
	private void plainRenderingWithBoundingBox(GL2 gl)
	{
		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
		
		// Clear old colors and depths.
		gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
		
		Camera cam = getCamera();
        Vector3d cameraUp = cam.getUp();
        Vector3d cameraForward = cam.getForward();
        Point3d cameraPos = cam.getPosition();
        Vector3d cameraTarget = new Vector3d(cameraPos);
        cameraTarget.add(cameraForward);
        
        plainProgram.useProgram(gl);
        
		// Set the view matrix.
        int viewMatrixId = plainProgram.getUniformLocation(gl, "viewMatrix");
		gl.glUniformMatrix4fv(viewMatrixId, 1, false,
				Util.matrixToArray(
						Util.getViewMatrix(new Vector3f(cameraPos),
											new Vector3f(cameraForward),
											new Vector3f(cameraUp))),
				0);

		// Render the first time with object.
		gl.glEnable(GL2.GL_DEPTH_TEST);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glLoadIdentity();
		simulator.draw(gl, false);
		
		// Render the second time with the bounding boxes;
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glLoadIdentity();
		simulator.draw(gl, true);
		
		plainProgram.unuseProgram(gl);
	}
	
	private void drawFPS(GLAutoDrawable drawable)
	{
		fpsTextRenderer.beginRendering(drawable.getWidth(), drawable.getHeight());
	    fpsTextRenderer.setColor(1.0f, 1.0f, 0.0f, 0.8f);
	    int frameCount = frameTimePoints.size();
	    long frameTime = frameTimePoints.getLast() - frameTimePoints.getFirst();
	    fpsTextRenderer.draw("" + (int)(frameCount / (frameTime / 1000000000.0)),
	    				canvasWidth - 80, canvasHeight - 80);
	    fpsTextRenderer.endRendering();
	}

	private void drawModesInfo(GLAutoDrawable drawable)
	{
		modeTextRenderer.beginRendering(drawable.getWidth(), drawable.getHeight());
		modeTextRenderer.setColor(0.2f, 0.2f, 1.0f, 0.8f);
		
		List<String> textsToRender = new ArrayList<String>();
		
	    if (stepping)
	    	textsToRender.add("Play mode(S/F): stepping");
	    else
	    	textsToRender.add("Play mode(S): normal");
	    
	    if (renderBoundingBoxMode)
	    	textsToRender.add("Render bounding box mode(B): on");
	    else
	    	textsToRender.add("Render bounding box mode(B): off");
	    
	    double maxWidth = 0;
	    double height = 0;
	    for (String str : textsToRender)
	    {
	    	Rectangle2D bounds = modeTextRenderer.getBounds(str);
	    	height = bounds.getHeight();
	    	if (bounds.getWidth() > maxWidth)
	    		maxWidth = bounds.getWidth();
	    }
	    
	    double posy = 120;
	    int marginToTop = 5;
	    int marginToRight = 5;
	    
	    for (String str : textsToRender)
	    {
	    	modeTextRenderer.draw(str, (int)(canvasWidth - maxWidth - marginToRight), (int)(canvasHeight - posy));
	    	posy += height + marginToTop;
	    }
	    
	    modeTextRenderer.endRendering();
	}
	
	private void directRenderingPass(GL2 gl)
	{
		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, mainFrameBufferId);
		
		// Clear old colors and depths.
		gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
		
		gl.glEnable(GL2.GL_DEPTH_TEST);

		// Wireframe mode.
//		gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_LINE);
		
		// NOTE: This is a hack, removing View matrix from ModelView matrix,
		// so that we can get model matrix along.
		renderWithShadowMapProgram.useProgram(gl);
		
        Camera cam = getCamera();
        Vector3d cameraUp = cam.getUp();
        Vector3d cameraForward = cam.getForward();
        Point3d cameraPos = cam.getPosition();
        Vector3d cameraTarget = new Vector3d(cameraPos);
        cameraTarget.add(cameraForward);
        
		// Active the texture.
        gl.glActiveTexture(GL2.GL_TEXTURE0);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, shadowMapTextureId);

		// Set the view matrix.
		long time = System.nanoTime();
        Matrix4f viewMatrix = Util.getViewMatrix(new Vector3f(cameraPos), new Vector3f(cameraForward), new Vector3f(cameraUp));
		gl.glUniformMatrix4fv(viewMatrixUniformId, 1, false,
				Util.matrixToArray(viewMatrix),
				0);
		
		// Set the shadow map texture.
		gl.glUniform1i(shadowMapUniformId, 0);
		
		// Set the light matrix.
		gl.glUniformMatrix4fv(lightMatrixUniformId, 1, false, Util.matrixToArray(getLightMatrix()), 0);
		
		// Set the light ambient color.
		gl.glUniform4fv(lightAmbientUniformId, 1, lightAmbient, 0);
		
		// Set the light diffuse color.
		gl.glUniform4fv(lightDiffuseUniformId, 1, lightDiffuse, 0);
		
		// Set the light specular color.
		gl.glUniform4fv(lightSpecularUniformId, 1, lightSpecular, 0);
		
		// Set the light direction.
		gl.glUniform4f(lightDirectionUniformId, lightDirection[0], lightDirection[1], lightDirection[2], 0);
		
		// Set the camera position.
		gl.glUniform4f(eyePositionUniformId, (float)cameraPos.x, (float)cameraPos.y, (float)cameraPos.z, 1.0f);

		// Here gl_ModelViewMatrix is only model matrix.
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glLoadIdentity();
		
		simulator.draw(gl, false);
		renderWithShadowMapProgram.unuseProgram(gl);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);

		gl.glFlush();
		
		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
	}
	
	private void deferredRendering(GL2 gl, GLUgl2 glu)
	{
		gl.glDisable(GL2.GL_DEPTH_TEST);
		
		// Set matrices
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPushMatrix();
		gl.glLoadIdentity();
		glu.gluOrtho2D(-1, 1, -1, 1);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glLoadIdentity();
		
		getAO(gl);
//		blurAO(gl);
		deferredShadingProgram.useProgram(gl);
		
		// Set the color texture.
		gl.glActiveTexture(GL2.GL_TEXTURE0);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, mainColorTextureId);
		gl.glUniform1i(colorTextureUniformId, 0);
		
		// Set the ao texture.
		gl.glActiveTexture(GL2.GL_TEXTURE1);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, aoTextureId);
		gl.glUniform1i(aoUniformId, 1);
		
		gl.glBegin(GL2.GL_QUADS);
			gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex2d(-1, -1);
			gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex2d(1, -1);
			gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex2d(1, 1);
			gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex2d(-1, 1);
		gl.glEnd();
		
		deferredShadingProgram.unuseProgram(gl);
		
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPopMatrix();
		
		gl.glEnable(GL2.GL_DEPTH_TEST);
	}
	
//	private void blurAO(GL2 gl)
//	{
//		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, blurFrameBufferId);
//		
//		gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
//		
//		blurProgram.useProgram(gl);
//		
//		// Set the ao texture.
//		gl.glActiveTexture(GL2.GL_TEXTURE0);
//		gl.glBindTexture(GL2.GL_TEXTURE_2D, aoTextureId);
//		int colorTextureId = blurProgram.getUniformLocation(gl, "aoTexture");
//		gl.glUniform1i(colorTextureId, 0);
//		
//		blurProgram.unuseProgram(gl);
//		
//		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
//	}

	private void getAO(GL2 gl)
	{
		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, aoFrameBufferId);
		
		gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
		
		aoProgram.useProgram(gl);
				
		// Set the normal texture.
		gl.glActiveTexture(GL2.GL_TEXTURE0);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, mainNormalTextureId);
		gl.glUniform1i(normalTextureUniformId, 0);
		
		// Set the position texture.
		gl.glActiveTexture(GL2.GL_TEXTURE1);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, mainPositionTextureId);
		gl.glUniform1i(positionTextureUniformId, 1);
		
		// Set the random texture.
		gl.glActiveTexture(GL2.GL_TEXTURE2);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, randomTextureId);
		gl.glUniform1i(randomTextureUniformId, 2);
		
		gl.glBegin(GL2.GL_QUADS);
			gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex2d(-1, -1);
			gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex2d(1, -1);
			gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex2d(1, 1);
			gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex2d(-1, 1);
		gl.glEnd();
		
		aoProgram.unuseProgram(gl);
		gl.glActiveTexture(GL2.GL_TEXTURE0);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
	}

	private void drawShadowMapToScreen(GL2 gl, GLUgl2 glu)
	{
		gl.glDisable(GL2.GL_DEPTH_TEST);
		
		// Set matrices
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPushMatrix();
		gl.glLoadIdentity();
		glu.gluOrtho2D(-1, 1, -1, 1);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glLoadIdentity();
		
		// Bind the shadow map and use the program.
		displayShadowMapProgram.useProgram(gl);
		gl.glActiveTexture(GL2.GL_TEXTURE0);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, shadowMapTextureId);
		
		gl.glUniform1i(textureUniformId, 0);
		
		gl.glBegin(GL2.GL_QUADS);
			gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex2d(-1, 0.5);
			gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex2d(-0.5, 0.5);
			gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex2d(-0.5, 1);
			gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex2d(-1, 1);
		gl.glEnd();
		displayShadowMapProgram.unuseProgram(gl);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPopMatrix();
		gl.glEnable(GL2.GL_DEPTH_TEST);
	}
	
	private Matrix4f getLightMatrix()
	{
		Vector3f targetDir = new Vector3f(lightDirection[0], lightDirection[1], lightDirection[2]);
		Vector3f up = new Vector3f(0, 1, 0);
		Vector3f pos = new Vector3f(lightPosition[0], lightPosition[1], lightPosition[2]);
		Matrix4f view = Util.getViewMatrix(pos, targetDir, up);
		Matrix4f orthProjection =
				Util.getOrthogonalProjectionMatrix(lightFrustum[0], lightFrustum[1], lightFrustum[2],
												lightFrustum[3], lightFrustum[4], lightFrustum[5]);
		
		// bias matrix is used to convert NDC ([-1, 1]^3) to ([0, 1]^3).
		// x and y can be converted directly (always ranged in 0 - 1),
		// z is controlled by glDepthRange, whose default values are 0 - 1,
		// in which case we can convert z in the same manner as x and y.
		Matrix4f bias = new Matrix4f();
		bias.m00 = 0.5f;
		bias.m03 = 0.5f;
		bias.m11 = 0.5f;
		bias.m13 = 0.5f;
		bias.m22 = 0.5f;
		bias.m23 = 0.5f;
		bias.m33 = 1;
		
		Matrix4f lightMatrix = new Matrix4f(bias);
		lightMatrix.mul(orthProjection);
		lightMatrix.mul(view);
		
		return lightMatrix;
	}
	
	private void renderShadowMap(GL2 gl, GLUgl2 glu) throws Exception
	{
		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, shadowMapFrameBufferId);
		
        // Clear old colors and depths.
		gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
		
		// Attach the depth texture to the FBO.
		gl.glActiveTexture(GL2.GL_TEXTURE0);
		gl.glBindTexture(GL2.GL_TEXTURE_2D, shadowMapTextureId);
		
		gl.glDrawBuffer(GL2.GL_NONE); // No color buffer is drawn to.
		 
		// Always check that our framebuffer is ok
		if (gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER) != GL2.GL_FRAMEBUFFER_COMPLETE)
			throw new Exception("Framebuffer is not ready!");
		
		// Set projection matrix.
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPushMatrix();
		gl.glLoadIdentity();
		gl.glMultMatrixf(Util.matrixToArray(
				Util.getOrthogonalProjectionMatrix(lightFrustum[0], lightFrustum[1], lightFrustum[2],
											lightFrustum[3], lightFrustum[4], lightFrustum[5])), 0);
		
		// Set view matrix (model matrix is identity).
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glPushMatrix();
		gl.glLoadIdentity();
		Vector3f targetDir = new Vector3f(lightDirection[0], lightDirection[1], lightDirection[2]);
		Vector3f up = new Vector3f(0, 1, 0);
		Vector3f pos = new Vector3f(lightPosition[0], lightPosition[1], lightPosition[2]);
		gl.glMultMatrixf(Util.matrixToArray(Util.getViewMatrix(pos, targetDir, up)), 0);
		
		// Save and reset the viewport.
		gl.glPushAttrib(GL2.GL_VIEWPORT_BIT);
		gl.glViewport(0, 0, shadowMapWidth, shadowMapHeight);
		
		// Render the scene to the shadow map.
		simulator.draw(gl, false);
		
		gl.glFlush();
        
        // Recover the viewport.
        gl.glPopAttrib();
		
		// Recover the matrices.
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPopMatrix();
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glPopMatrix();
		
		gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
		
		gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
	}

	/**
     * Called by OpenGL every time we want to redraw.
     *
     * @param GLAutoDrawable a graphics context
     */
	public void display(GLAutoDrawable drawable_)
    {
        if (!ready)
        {
            return;
        }

        updateSimulation();
        render(drawable_);

        if (frameExporter != null)
        {
            frameExporter.writeFrame();
        }
        
        Util.traceTime("Render time", System.nanoTime() - lastFrameTime - Timing.UpdateRigidBodyTime);
        lastFrameTime = System.nanoTime();
	}

    /**
     * Set frame rate of our application. Force our application to draw
     * itself at least once per second, for simplicity.
     *
     * @param int frame rate (positive)
     */
    public final void setFrameRate(int frameRate_)
    {
        if (frameRate_ <= 0)
        {
            throw new IllegalArgumentException("Application frame rate must be positive.");
        }

        if (frameRate == frameRate_)
        {
            return;
        }

        frameRate = frameRate_;

        // Reset the animator with correct frame rate, if necessary.
        if (animator != null)
        {
            boolean started = animator.isAnimating();
            if (started)
            {
                stopAnimation();
            }
		    animator = new FPSAnimator(panel, frameRate);
            if (started)
            {
                startAnimation();
            }
        }
    }
	
    /**
     * Resize application. We redefine to where OpenGL is allowed to draw.
     *
     * @param GLAutoDrawable graphics context
     * @param int x coordinate
     * @param int y coordinate
     * @param int new width
     * @param int new height
     */
	public void reshape(GLAutoDrawable drawable_, int x_, int y_,
            int width_, int height_)
    {
		GL2 gl = drawable_.getGL().getGL2();
		GLUgl2 glu = new GLUgl2();

        if (width_  <= 0) width_  = 1;
		if (height_ <= 0) height_ = 1;

        setSize(width_, height_);
		gl.glViewport(0, 0, width_, height_);

		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluPerspective(45.0f, (float)width_/(float)height_, 0.1f, 100.0f);

		gl.glMatrixMode(GL2.GL_MODELVIEW);
	}

    /**
     * Clean up OpenGL objects (like display lists).
     */
	public void dispose(GLAutoDrawable drawable_)
    {
	}
	
    /**
     * Begin animation (which is calling the display method).
     */
	public final void startAnimation()
    {
        assert animator != null;
		animator.start();
	}

    /**
     * Stop animation (which is calling the display method).
     */
	public final void stopAnimation()
    {
        assert animator != null;
		animator.stop();
	}

    /**
     * Begin and stop capturing sceenshots.
     */
	protected final void toggleFrameExporter()
    {
		if (frameExporter == null)
        {
			frameExporter = new FrameExporter();
			System.out.println("Beginning capture...");
		}
		else
        {
			frameExporter = null;
			System.out.println("Ending capture");
		}
	}

    /**
     * Keeps track of the number of exports.
     */
	private static int ExportId = 0;
	
    /**
     * The FrameExporter class provides basic screen capture 
     * functionality.
     */
	private class FrameExporter
    {
        private int _exportId;
		private int _nFrame;

		FrameExporter()
        { 
            _nFrame = 0;
            _exportId = ExportId++;
		}

		void writeFrame()
		{ 
			long timeNS = -System.nanoTime();
			String flname = String.format("export%d-%05d.png", _exportId, _nFrame);

			try
            {  
				System.out.println(flname);
				File file = new File(flname);
				if (file.exists())
                {
                    System.out.println("Warning: Overwriting previous file " + flname);
                }

				Screenshot.writeToFile(file, width, height);

				timeNS += System.nanoTime();
				System.out.println((timeNS/1000000) + "ms: Wrote image " + flname);
			}
            catch(Exception e)
            { 
				e.printStackTrace();
				System.out.println("Error: " + e); 
			} 

			_nFrame++;
		}
    }

	public final int getWidth()
    {
        assert width > 0;
        return width;
    }

    public final int getHeight()
    {
        assert height > 0;
        return height;
    }

    /**
     * Set window size of our application.
     *
     * @param int width (positive)
     * @param int height (positive)
     */
    public final void setSize(int width_, int height_)
    {
        if (width_ <= 0 || height_ <= 0)
        {
            throw new IllegalArgumentException("Application width and height must be positive.");
        }

        width = width_;
        height = height_;
    }

    public final GLJPanel getPanel()
    {
        assert panel != null;
        return panel;
    }

    public final Camera getCamera()
    {
        assert camera != null;
        return camera;
    }

    public final int getFrameRate()
    {
        assert frameRate > 0;
        return frameRate;
    }
    
    /**
     * Get whether the application is ready to receive OpenGL drawing
     * events or not.
     *
     * @return boolean
     */
    public final boolean isReady()
    {
        return ready;
    }
    
    public static void main(String[] args)
	{
		MotionSimulator ga = new MotionSimulator();
		ga.startAnimation();
	}

	public void toggleStepping()
	{
		stepping = !stepping;
	}

	public void stepping()
	{
		if (stepping)
			forwardStep = true;
	}

	public void toggleRenderBoundingBoxMode()
	{
		renderBoundingBoxMode = !renderBoundingBoxMode;
	}
}
